/*
 * $Id: Node.java 1259 2007-01-09 14:23:47Z tcoupaye $
 *
 * Behavior Protocols extensions for static and runtime checking
 * developed for the Julia implementation of Fractal.
 *
 * Copyright (C) 2006
 *    Formal Methods In Software Engineering Group
 *    Institute of Computer Science
 *    Academy of Sciences of the Czech Republic
 *
 * Copyright (C) 2006 France Telecom
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact: ft@nenya.ms.mff.cuni.cz
 * Authors: Jiri Adamek <adamek@nenya.ms.mff.cuni.cz>
 *
 */

package org.objectweb.fractal.bpc.staticchecker.preprocessor;

import java.util.Vector;


/**
 * This class represents a node in a parse tree. 
 * The {@link #EVENT}, {@link #UNARY}, {@link #BINARY},
 * {@link #CALL}, and {@link #ATOMIC}
 * constants denote possible types of nodes (they are used as values
 * assigned to the {@link #type} field).  
 * In the description of those constants, P, Q are protocols, m is a method name.
 */
public class Node implements Cloneable {
	
	/** The node represents the NULL keyword (empty subprotocol). */
	public static final int NULL = 0;
	
	/** 
	 * The node represents an event.
	 * The event is denoted by one of the tokens 
	 * <code>?m^</code>, <code>!m^</code>, <code>?m$</code>, or <code>!m$</code>. 
	 */
	public static final int EVENT = 1;

	/**
	 * The node (the corresponding subtree) represents a method call.
	 * The call is denoted as <code>?m</code>, <code>!m</code>,
	 * <code>?m{P}</code>, or <code>!m{P}</code>, where <code>P</code>
	 * is a protocol.
	 */
	public static final int CALL = 2;

	/**
	 * The node (the corresponding subtree) represents an atomic action. The action
	 * has the form
	 * [t1, t2, ..., tn], where each ti is ?m^, !m^, ?m$, or !m$.
	 */
	public static final int ATOMIC = 3;
	
	/**
	 * The node (the corresponding subtree) represents 
	 * an unary operation.
	 * I.e., a protocol of the form <code>P u</code>, where <code>u</code> is
	 * an unary operator.
	 */
	public static final int UNARY = 4;

	/**
	 * The node (the corresponding subtree) represents 
	 * a binary operation. I.e., a protocol of the form <code>P b Q</code>, where 
	 * <code>b</code>
	 * is a binary operator.
	 */
	public static final int BINARY = 5; 

	/**
	 * The operators used in behavior protocols, in the order of increasing
	 * priority.
	 */
	public static final String operators[] = { "+", ";", "|", "||", "*" };
	
	/**
	 * Arity of the operators. The order is the same the order as in the {@link #operators}
	 * array. Every field is one of the constants {@link #UNARY},
	 * {@link #BINARY}.
	 */
	public static final int arity[] = { BINARY, BINARY, BINARY, BINARY, UNARY };
	
	/**
	 * Returns the priority of a given operator, i.e., its index in the
	 * {@link #operators} array.
	 */
	public static int getOperatorPriority(String operator) {
		for (int i = 0; i < operators.length; i++)
			if (operator.equals(operators[i]))
				return i;
		/* TODO throw an exception here */
		return -1;
	}
	
	/** 
	 * The type of the node.
	 * The value is one of the following: {@link #EVENT}, {@link #CALL},
	 * {@link #ATOMIC}, {@link #UNARY}, {@link #BINARY}.
	 */
	public int type;

	/* Valid for the following node types: UNARY, BINARY. */
	public String operator = null;

	/* Valid for the following node types: EVENT, CALL. */
	public char prefix = '\0';

	/* Valid for the following node types: EVENT, CALL. */
	public Name name = null;

	/* Valid for the following node types: EVENT.*/
	public char suffix = '\0';

	/* 
	 * Valid for the following node types: CALL (in the cases ?m{P}, !m{P},
	 * otherwise null), UNARY, BINARY.
	 */
	public Node left = null;

	/* Valid for the following node types: BINARY. */
	public Node right = null;

	/* Valid for the following types: ATOMIC. */
	public Node[] eventArray = null;
	
	public Node() {
	}

	public Node cloneNode() {
		Node newNode;
		
		try {
			newNode = (Node) super.clone();
		} catch(CloneNotSupportedException e) {
			return null;
		}
		
		if (left != null) left = left.cloneNode();
		if (right != null) right = right.cloneNode();
		if (name != null) name = name.cloneName();

		if (eventArray != null) {
			for (int i = 0; i < eventArray.length; i++) {
				if (eventArray[i] != null)
					eventArray[i] = eventArray[i].cloneNode();
			}
		}
		
		return newNode;
	}
	
	public Node setNull() {
		this.type = NULL;
		return this;
	}
	
	public Node setEvent(char prefix, Name name, char suffix) {
		this.type = EVENT;
		this.prefix = prefix;
		this.name = name;
		this.suffix = suffix;
		return this;
	}
	
	public Node setCall(char prefix, Name name, Node subprotocol) {
		this.type = CALL;
		this.prefix = prefix;
		this.name = name;
		this.left = subprotocol;
		return this;
	}
	
	/* TODO throw an exception if any of the elements in array_of_events is
	 * not of the type EVENT or array_of_events has zero elements.
	 */
	public Node setAtomic(Node[] array_of_events) {
		this.type = ATOMIC;
		this.eventArray = array_of_events;
		return this;
	}
	
	public Node setUnary(String operator, Node left) {
		this.type = UNARY;
		this.operator = operator;
		this.left = left;
		return this;
	}
	
	public Node setBinary(String operator, Node left, Node right) {
		this.type = BINARY;
		this.operator = operator;
		this.left = left;
		this.right = right;
		return this;
	}

	public String toString() {
		String leftResult, rightResult;
		switch (type) {
		case NULL:
			return "NULL";
		case EVENT:
			return prefix + name.toString() + suffix;
		case CALL:
			if (left != null)
				return prefix + name.toString() + '{' + left.toString() + '}';
			else
				return prefix + name.toString();
		case ATOMIC:
			String result = new String();
			Node n = eventArray[0];
			result = result + n.prefix + n.name + n.suffix;
			for (int i = 1; i < eventArray.length; i++) {
				n = eventArray[i];
				result = result + ", " + n.prefix + n.name + n.suffix;
			}
			return '[' + result + ']';
		case UNARY:
			if ((left.type == UNARY || left.type == BINARY)
					&& getOperatorPriority(left.operator) < getOperatorPriority(operator))
				leftResult = '(' + left.toString() + ')';
			else
				leftResult = left.toString();
			return leftResult + operator;
		case BINARY:
			if ((left.type == UNARY || left.type == BINARY)
					&& getOperatorPriority(left.operator) < getOperatorPriority(operator))
				leftResult = '(' + left.toString() + ')';
			else
				leftResult = left.toString();
			if ((right.type == UNARY || right.type == BINARY)
					&& getOperatorPriority(right.operator) < getOperatorPriority(operator))
				rightResult = '(' + right.toString() + ')';
			else
				rightResult = right.toString();
			return leftResult + ' ' + operator + ' ' + rightResult;
		default:
			/* TODO throw an exception here */
			return null;
		}
	}

	private static String getSpaces(int n) {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < n; i++)
			buffer.append(' ');
		return buffer.toString();
	}
	
	private static Vector mergeDebugInfo(Vector info1, Vector info2) {
		final int between = 3;
		int min, max;
		if (info1.size() < info2.size()) {
			min = info1.size();
			max = info2.size();
		} else {
			min = info2.size();
			max = info1.size();
		}
		Vector result = new Vector();
		for (int i = 0; i < min; i++) {
			result.add((String) info1.get(i) + getSpaces(between) + (String) info2.get(i));
		}
		if (info1.size() > info2.size()) {
			for (int i = min; i < max; i++) {
				result
						.add((String) info1.get(i)
								+ getSpaces(between
										+ ((String) info2.get(0)).length()));
			}
		} else if (info2.size() > info1.size()) {
			for (int i = min; i < max; i++) {
				result
						.add(getSpaces(between
								+ ((String) info1.get(0)).length())
								+ (String) info2.get(i));
			}
		}
		return result;
	}
	
	private static void extendDebugInfo(Vector info, int numberOfSpaces) {
		int n = info.size();
		for (int i = 0; i < n; i++) {
			info.setElementAt(extendString((String)info.get(i), numberOfSpaces), i);
		}
	}

	private static String extendString(String s, int numberOfSpaces) {
		int pre = numberOfSpaces / 2;
		int post = numberOfSpaces - pre;
		return getSpaces(pre) + s + getSpaces(post);
	}
	
	private Vector getDebugInfo() {
		String s;
		Vector info1 = null;
		Vector info2 = null;
		switch (type) {
		case NULL:
			s = "NULL";
			break;
		case EVENT:
			s =  prefix + name.toString() + suffix;
			break;
		case CALL:
			if (left != null) {
				s = prefix + name.toString() + "{...}";
				info1 = left.getDebugInfo();
			} else {
				s = prefix + name.toString();
			}
			break;
		case ATOMIC:
			s = this.toString();
			break;
		case UNARY:
			s = operator;
			info1 = left.getDebugInfo();
			break;
		case BINARY:
			s = operator;
			info1 = left.getDebugInfo();
			info2 = right.getDebugInfo();
			break;
		default:
			/* TODO add assert(false); */
			s = "!!! INTERNAL ERROR !!!";
		}	
		if (info1 == null && info2 == null) {
			Vector v = new Vector();
			v.add(s);
			return v;
		}
		if (info2 != null) 
			info1 = mergeDebugInfo(info1, info2);
		int infoLength = ((String)info1.get(0)).length();
		if (infoLength > s.length())
			s = extendString(s, infoLength - s.length());
		else if (s.length() > infoLength)
			extendDebugInfo(info1, s.length() - infoLength);
		info1.add(0, s);
		return info1;
	}

	public String getDebugString() {
		String result = "";
		Vector v = getDebugInfo();
		for (int i = 0; i < v.size(); i++)
			result = result + (String) v.get(i) + "\n";
		return result;
	}
}
